/* Copyright (c) 2015,2016 Lucky Byte, Inc.
 */
package com.lucky_byte.pay.sched;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.quartz.CronExpression;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.Trigger;
import org.quartz.TriggerKey;
import org.quartz.impl.StdSchedulerFactory;
import com.lucky_byte.pay.jar.JdbcRecord;
import static org.quartz.JobBuilder.*;
import static org.quartz.TriggerBuilder.*;

import static org.quartz.CronScheduleBuilder.*; 
import com.lucky_byte.pay.jar.Runtime;

/**
 * 对 Quartz Scheduler 的单实例封装.
 */
public class Sched
{
	private static final Logger logger = LogManager.getLogger();
	static private Sched instance = null;

	private static final String DEFAULT_JOB_GROUP = "JOBS";

	public static final int ENGINE_LUA    = 1;
	public static final int ENGINE_SHELL  = 2;
	public static final int ENGINE_PYTHON = 3;
	public static final int ENGINE_RUBY = 4;
	public static final int ENGINE_PERL = 5;

	public static Sched getInstance() {
		if (instance == null) {
			String path = Runtime.pathFor("/etc/quartz.properties");
			instance = new Sched(path);
		}
		return instance;
	}
	private Scheduler scheduler;

	private Sched(String properties) {
		try {
			StdSchedulerFactory factory = new StdSchedulerFactory();
			factory.initialize(properties);
			scheduler = factory.getScheduler();
		} catch (SchedulerException e) {
			logger.error("初始化任务调度器错误[{}].", e.getMessage());
		}
	}

	/**
	 * 启动调度器
	 */
	public void start() {
		try {
			if (scheduler != null && !scheduler.isStarted()) {
				scheduler.start();
			}
		} catch (SchedulerException e) {
			logger.error("启动任务调度器错误[{}].", e.getMessage());
		}
	}

	/**
	 * 停止调度器
	 */
	public void shutdown() {
		try {
			if (scheduler != null) {
				scheduler.shutdown(true);
			}
		} catch (SchedulerException e) {
			logger.error("停止任务调度器错误[{}][{}].", e.getMessage(),
					e.getClass().getSimpleName());
		} finally {
			scheduler = null;
		}
	}

	/**
	 * 添加调度任务
	 */
	public boolean sched(JdbcRecord record) {
		if (record == null) {
			logger.error("sched()参数[record]不能为空，调度任务失败.");
			return false;
		}
		this.start();

		switch (record.getInteger("engine")) {
		case ENGINE_SHELL:
			return schedJob(record, ShellJob.class);
		case ENGINE_LUA:
			return schedJob(record, LuaJob.class);
		case ENGINE_PYTHON:
			return schedJob(record, PythonJob.class);
		case ENGINE_RUBY:
			return schedJob(record, RubyJob.class);
		case ENGINE_PERL:
			return schedJob(record, PerlJob.class);
		default:
			logger.error("不支持调度引擎[{}].", record.getInteger("engine"));
			return false;
		}
	}

	/**
	 * 添加任务
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private boolean schedJob(JdbcRecord record, Class job_class) {
		String cron_expression = record.getString("cron");
		if (cron_expression == null || cron_expression.trim().length() == 0) {
			logger.error("任务[{}]未配置 CRON 表达式.", record.getString("name"));
			return false;
		}
		if (!CronExpression.isValidExpression(cron_expression)) {
			logger.error("任务[{}]配置的 CRON 表达式[{}]无效.",
					record.getString("name"), cron_expression);
			// 获取表达式错误的具体原因
			try {
				CronExpression.validateExpression(cron_expression);
			} catch (Exception e) {
				logger.error("{}", e.getMessage());
			}
			return false;
		}
		try {
			JobDetail detail = newJob(job_class)
					.withIdentity("Job-" + record.getInteger("serial"),
							DEFAULT_JOB_GROUP)
					.withDescription(record.getString("notes"))
					.usingJobData("serial", record.getInteger("serial"))
					.usingJobData("name", record.getString("name"))
					.usingJobData("script", record.getString("script"))
					.usingJobData("params", record.getString("params"))
					.build();
			Trigger trigger = newTrigger()
					.withIdentity("Trigger-" + record.getInteger("serial"),
							DEFAULT_JOB_GROUP)
					.withDescription(cron_expression)
					.withSchedule(cronSchedule(cron_expression))
					.build();
			scheduler.scheduleJob(detail, trigger);
			return true;
		} catch (Exception e) {
			logger.error("添加调度任务[{}]错误[{}][{}].", record.getString("name"),
					e.getMessage(), e.getClass().getSimpleName());
			return false;
		}
	}

	/**
	 * 删除已调度的任务.
	 */
	public boolean unsched(JdbcRecord record) {
		return this.unsched(record.getInteger("serial"));
	}

	/**
	 * 删除已调度的任务.
	 */
	public boolean unsched(int serial) {
		return unsched(Integer.toString(serial));
	}

	/**
	 * 删除已调度的任务.
	 */
	public boolean unsched(String serial) {
		try {
			String name = "Job-" + serial;
			JobKey key = JobKey.jobKey(name, DEFAULT_JOB_GROUP);
			if (!scheduler.checkExists(key)) {
				logger.info("待删除的调度任务[{}]不存在.", name);
				return true;
			}
			return scheduler.deleteJob(key);
		} catch (SchedulerException e) {
			logger.error("删除调度任务[{}]错误[{}].", serial, e.getMessage());
			return false;
		}
	}

	/**
	 * 立即触发任务
	 */
	public boolean trigger(JdbcRecord record) {
		if (record == null) {
			logger.error("参数[record]不能为空，调度任务失败.");
			return false;
		}
		this.start();

		switch (record.getInteger("engine")) {
		case ENGINE_SHELL:
			return triggerJob(record, ShellJob.class);
		case ENGINE_LUA:
			return triggerJob(record, LuaJob.class);
		case ENGINE_PYTHON:
			return triggerJob(record, PythonJob.class);
		case ENGINE_RUBY:
			return triggerJob(record, RubyJob.class);
		case ENGINE_PERL:
			return triggerJob(record, PerlJob.class);
		default:
			logger.error("不支持调度引擎[{}].", record.getInteger("engine"));
			return false;
		}
	}

	/**
	 * 触发任务
	 */
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private boolean triggerJob(JdbcRecord record, Class job_class) {
		try {
			JobDetail detail = newJob(job_class)
					.withIdentity(JobKey.createUniqueName(DEFAULT_JOB_GROUP))
					.withDescription(record.getString("notes"))
					.usingJobData("serial", record.getInteger("serial"))
					.usingJobData("name", record.getString("name"))
					.usingJobData("script", record.getString("script"))
					.usingJobData("params", record.getString("params"))
					.build();
			Trigger trigger = newTrigger()
					.withIdentity(TriggerKey.createUniqueName(DEFAULT_JOB_GROUP))
					.startNow()
					.build();
			scheduler.scheduleJob(detail, trigger);
			return true;
		} catch (Exception e) {
			logger.error("触发任务[{}]错误[{}][{}].", record.getString("name"),
					e.getMessage(), e.getClass().getSimpleName());
			return false;
		}
	}

}
